#include "MoreGui.h"

#include "App/Game.h"
#include "App/Window.h"
#include "World/Player.h"

#include <glm/gtc/type_ptr.hpp>

void MoreGui::OnInit()
{
  LOG_INFO("Plugin: \"{}\" Init", GetName());
}

void MoreGui::OnUpdate()
{
  m_deltaTime = Game::GetInstance().GetDeltaTime();
}

void MoreGui::OnRenderGui()
{
  auto uictx = Gui::GetInstance().GetContext();
  if (!uictx)
    return;

  ImGui::SetCurrentContext(uictx);
  ImGui::Begin("Debug");
  ImGui::Text("FPS: %.2f", 1.0f / m_deltaTime);

  auto &window = Window::GetInstance();
  auto &game = Game::GetInstance();
  const auto &level = game.GetLevel();
  const auto &player = level->GetPlayer();

  auto position = player->GetPosition();
  if (ImGui::DragFloat3("Position", glm::value_ptr(position)))
    player->SetPosition(position);

  auto rotDegreed = glm::degrees(player->GetRotation());
  if (ImGui::DragFloat3("Rotation", glm::value_ptr(rotDegreed)))
    player->SetRotation(glm::radians(rotDegreed));

  auto direction = player->GetDirection();
  ImGui::Text("Direction: %s", direction.ToString().c_str());

  if (player->HasRayCastHit())
  {
    const HitTarget &hit = player->GetHitTarget();
    const auto pos = hit.blockPosition;
    ImGui::Text("Target position: %d, %d, %d", pos.x, pos.y, pos.z);
    ImGui::Text("Target block: %s", hit.blockType->name.c_str());
    
    uint8_t lightLevel = 0;
    if (hit.hasNeighbor)
    {
      const auto chunkIndex = World::GetChunkIndex(hit.neighborPosition);
      auto chunk = level->GetWorld()->GetChunk(chunkIndex);
      if (chunk)
      {
        const auto neighborPosRelative = World::ToPositionInChunk(hit.neighborPosition);
        lightLevel = chunk->vertexLightData[neighborPosRelative.x][neighborPosRelative.y][neighborPosRelative.z];
      }
    }
    ImGui::Text("Target light level: %d", lightLevel);
  }
  else
  {
    ImGui::Text("Target position: Empty");
    ImGui::Text("Target block: Empty");
    ImGui::Text("Target light level: 0");
  }

  auto mode = Game::GetInstance().GetGameMode();
  bool isCreative = mode == Game::Mode::Creative;
  if (ImGui::Checkbox("Flying", &isCreative))
  {
    Game::GetInstance().SetGameMode(
        mode == Game::Mode::Creative ? Game::Mode::Survival : Game::Mode::Creative);
  }

  if (ImGui::TreeNodeEx(
          "Properties",
          ImGuiTreeNodeFlags_Framed |
              ImGuiTreeNodeFlags_SpanAvailWidth |
              ImGuiTreeNodeFlags_AllowItemOverlap |
              ImGuiTreeNodeFlags_FramePadding))
  {

    ImGui::DragFloat("Walk Speed", &player->walkingSpeedMultiplier, 0.1f, 0.1f, 10.0f);
    ImGui::DragFloat("Run Speed", &player->runningSpeedMultiplier, 0.1f, 0.1f, 10.0f);
    ImGui::DragFloat("Jump Speed", &player->jumpSpeedMultiplier, 0.1f, 0.1f, 10.0f);
    ImGui::DragFloat("Rotating Speed", &player->rotatingSpeed, 0.1f, 0.1f, 10.0f);
    ImGui::DragFloat("Mouse Sensitivity", &player->mouseSensitivity, 0.1f, 0.1f, 10.0f);
    ImGui::DragFloat("Gravity Constant", &player->gravityConstant, 0.1f, 0.1f, 100.0f);
    ImGui::DragFloat("Reach", &player->reach, 0.1f, 0.1f, 16.0f);
    ImGui::Checkbox("Auto Jump", &player->autoJump);

    ImGui::TreePop();
  }

  Ref<BlockRegistry> blockReg = level->GetBlockRegistry();

  const auto blockToPlace = blockReg->GetBlock(player->blockToPalce.id);
  if (ImGui::BeginCombo("In Hand", blockToPlace->name.c_str()))
  {
    for (const auto &[blockName, id] : blockReg->GetAll())
    {
      if (ImGui::Selectable(blockName.c_str()))
      {
        player->blockToPalce.id = id;
      }
    }
    ImGui::EndCombo();
  }

  Skybox &skybox = level->GetSkybox();
  float time = skybox.GetTime();
  if (ImGui::DragFloat("Time", &time, 0.1f, 0.0f, skybox.GetDayLength()))
    skybox.SetTime(time);
  float timeSpeed = skybox.GetTimeSpeed();
  if (ImGui::DragFloat("Time Speed", &timeSpeed, 0.1f, 0.0f, 256.f))
    skybox.SetTimeSpeed(timeSpeed);

  if (ImGui::TreeNodeEx(
          "Video",
          ImGuiTreeNodeFlags_Framed |
              ImGuiTreeNodeFlags_SpanAvailWidth |
              ImGuiTreeNodeFlags_AllowItemOverlap |
              ImGuiTreeNodeFlags_FramePadding))
  {
    Ref<Camera> camera = player->GetCamera();
    float fov = camera->GetFov();
    if (ImGui::DragFloat("FOV", &fov, 0.1f, 0.0f, 180.0f))
      camera->SetFov(fov);

    int viewDistance = level->GetWorld()->GetViewDistance();
    if (ImGui::DragInt("View Distance", &viewDistance, 1, 1, 16))
      level->GetWorld()->SetViewDistance(viewDistance);

    ImGui::TreePop();
  }

  if (ImGui::TreeNodeEx(
          "Block Atlas",
          ImGuiTreeNodeFlags_Framed |
              ImGuiTreeNodeFlags_SpanAvailWidth |
              ImGuiTreeNodeFlags_AllowItemOverlap |
              ImGuiTreeNodeFlags_FramePadding))
  {
    ImVec2 available = ImGui::GetContentRegionAvail();
    Ref<Atlas> atlas = level->GetWorld()->GetAtlas();
    ImVec2 size = ImVec2(atlas->GetWidth(), atlas->GetHeight());
    const float aspect = size.x / size.y;
    size.x = available.x;
    size.y = available.x / aspect;
    ImGui::Image((ImTextureID)atlas->GetID(), size, ImVec2(0, 0), ImVec2(1, 1));
    ImGui::TreePop();
  }

  if (ImGui::TreeNodeEx(
          "Frame Buffers",
          ImGuiTreeNodeFlags_Framed |
              ImGuiTreeNodeFlags_SpanAvailWidth |
              ImGuiTreeNodeFlags_AllowItemOverlap |
              ImGuiTreeNodeFlags_FramePadding))
  {
    ImVec2 available = ImGui::GetContentRegionAvail();
    ImVec2 childSize(available.x, glm::min(available.y, 200.0f));
    ImGui::BeginChild(
        "Frame Buffers",
        childSize,
        ImGuiChildFlags_AlwaysAutoResize |
            ImGuiChildFlags_AutoResizeX);

    auto fbStack = window.GetFrameBufferStack();
    int i = 0;
    for (auto &t : fbStack->GetIntermediateTextures())
    {
      ImGui::Text("%d", i++);
      ImVec2 size = ImVec2(t->GetWidth(), t->GetHeight());
      const float aspect = size.x / size.y;
      size.x = available.x;
      size.y = available.x / aspect;
      ImGui::Image((ImTextureID)t->GetID(), size, ImVec2(0, 1), ImVec2(1, 0));
    }
    ImGui::EndChild();
    ImGui::TreePop();
  }

  ImGui::End();
}